package everything;


import graphics.server.Boom;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Vector;
import java.util.Map.Entry;

import markov.MDP;
import markov.MDPStateTable;

import simulatorGUI.DisplayWorld;
import simulatorGUI.Header;

import ch.ubique.inieditor.IniEditor;

import bnetwork.Block;
import bnetwork.ClimateNetwork;
import bnetwork.Node;
import bnetwork.ProbabilityPack;
import bnetwork.Region;


public class WorldMap {
	public static final int EMPTY = 10;
	public static final int WALL = 11;
	public static final int ICE = 13;
	public static final int FLAG = 15;
	public static final int OUTOFBOUNDS = 16;
	public static final int AGENT = 17;

	public static final int ICE_YES = 0;
	public static final int ICE_NO = 1;
	public static final int ICE_UNKNOWN = 2;


	protected int[][] _board;

	protected int _movesLeft;

	private Region[] _regions;
	private ProbabilityPack _p;

	private Map<Integer,Integer> _ices;
	private int[] _icesInverted;
	private int[] _flagsInverted;
	private Map<Integer,Flag> _flags;
	
	protected Agent _agent;
	private int[] _blocksSensedCounter;
	private boolean _remember;

	public WorldMap(String level, int moves, boolean remember){
		_board = readFromFile(level+".txt");
		_regions = makeRegions(level +".ini");
		_agent = null;
		
		_remember = remember;
		
		_movesLeft = moves;
		_ices = new HashMap<Integer, Integer>();
		_flags = new HashMap<Integer,Flag>();
	}

	/**
	 *
	 * @param x row
	 * @param y column
	 * @return
	 */
	public int getCellNumber(int x, int y){
		return (y+x*_board[0].length);
	}

	private int[][] readFromFile(String filename){
		try {
			BufferedReader In = new BufferedReader(new InputStreamReader(new FileInputStream(filename)));
			Vector<int[]> lines = new Vector<int[]>();
			String line;
			int j = 0; 
			while ((line=In.readLine())!=null){
				int[] tmp = new int[line.length()];
				for (int i=0; i<line.length(); i++){
					char ch = line.charAt(i);
					switch(ch){
					case '#':
						tmp[i] = WALL;
						break;
					case 'F':
						tmp[i] = FLAG;
						break;
					case ' ':
						tmp[i] = EMPTY;
						break;
					case 'A':
						tmp[i] = EMPTY;
						break;
					default:
						if (ch <= '9' && ch >='0'){
							tmp[i] = (ch-'0');
						} else {
							tmp[i] = EMPTY;
						}
					}
				}
				lines.add(tmp);
				j++;
			}
			return lines.toArray(new int[lines.size()][]);

		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.out.println("File not found - exiting");
			System.exit(0);
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Cannot read from file - exiting");
			System.exit(0);
			return null;
		}
	}

	private Region[] makeRegions(String probfile) {
		IniEditor probs = new IniEditor();
		try{
			probs.load(probfile);
			float summer = Float.parseFloat(probs.get("summer", "summer"));
			float warmProb = Float.parseFloat(probs.get("ice", "warm"));
			float coldProb = Float.parseFloat(probs.get("ice", "cold"));
			float frigidProb = Float.parseFloat(probs.get("ice", "frigid"));
			float closeProb = Float.parseFloat(probs.get("sensor", "close"));
			float farProb = Float.parseFloat(probs.get("sensor", "far"));
			_p = new ProbabilityPack(summer, closeProb, farProb, warmProb, coldProb, frigidProb);
			HashMap<Integer,Region> tmpRegions = new HashMap<Integer, Region>();
			for (int i=0; i<_board.length; i++){
				for (int j=0; j< _board[i].length; j++){
					int val = _board[i][j];
					if (val<=9 && val>=0){
						Region r = tmpRegions.get(new Integer(val));
						if (r == null){
							float pSW = Float.parseFloat(probs.get(val+"", "summerWarm"));
							float pSC = Float.parseFloat(probs.get(val+"", "summerCold"));
							float pSF = Float.parseFloat(probs.get(val+"", "summerFrigid"));
							float pWW = Float.parseFloat(probs.get(val+"", "winterWarm"));
							float pWC = Float.parseFloat(probs.get(val+"", "winterCold"));
							float pWF = Float.parseFloat(probs.get(val+"", "winterFrigid"));
							r = new Region(val, pSW, pSC, pSF, pWW, pWC, pWF);
							tmpRegions.put(new Integer(val), r);
						}
						r.addBlock(new Block(i, j, val, getCellNumber(i, j)));
					}
				}
			}
			return tmpRegions.values().toArray(new Region[tmpRegions.size()]);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.out.println("File not found - exiting");
			System.exit(0);
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Cannot read from file - exiting");
			System.exit(0);
			return null;
		}
	}

	public void printBoard(){
		for (int i=0; i< _board.length; i++){
			for (int j=0; j<_board[i].length; j++){
				System.out.print(_board[i][j]);
			}
			System.out.println();
		}
		System.out.println("done");
	}


	public int brickStatus(int x, int y){
		if (x < 0 || y < 0 || x >= _board.length || y >= _board[0].length) return OUTOFBOUNDS;
		return _board[x][y];
	}

	// returns true if cell (x,y) blocks agent movement
	public boolean isMovementBlocked(int x, int y) {
		if (x < 0 || y < 0 || x >= getHeight() || y >= getWidth()) return true;
		if (_board[x][y] == WALL) return true;
		return false;
	}

	public boolean isIce(int x, int y) {
		return _board[x][y] <= 9 && _board[x][y]>=0;
	}

	public int getHeight(){
		return _board.length;
	}

	public int getWidth(){
		return _board[0].length;
	}

	private boolean UserSense(ClimateNetwork c){
		Scanner sc = new Scanner(System.in);
		System.out.println("Enter a patch no. you want to sense, or 0 to skip");
		System.out.println("Available: ");

		for (int i=0; i<_board.length; i++){
			for (int j=0; j<_board[i].length; j++){
				if (_board[i][j]<=9 && _board[i][j]>=0 && _blocksSensedCounter[getCellNumber(i, j)]<2){
					System.out.print(getCellNumber(i, j) + " ");
				}
			}
		}
		System.out.println();
		int next = sc.nextInt();
		sc.reset();
		System.out.println("Type I for 'Ice', N for 'No-Ice' or anything else for random");
		String ans = sc.next();
		if (next!=0){
			Node[] blocksToSense= c.getBlockArr(next);
			if (blocksToSense!=null){
				int numOfBlockScans = _blocksSensedCounter[next];
				if (numOfBlockScans < 2){ // can scan
					_blocksSensedCounter[next]++;
					if (ans.equals("I")){
						c.addEvidence(blocksToSense[numOfBlockScans+1], ClimateNetwork.SENSE_ICE);
					} else if (ans.equals("N")){
						c.addEvidence(blocksToSense[numOfBlockScans+1], ClimateNetwork.SENSE_NONE);
					}else if (Math.random() < blocksToSense[numOfBlockScans+1].getProb(ClimateNetwork.SENSE_ICE)){
						//						blocksToSense[numOfBlockScans+1].infer(1);
						c.addEvidence(blocksToSense[numOfBlockScans+1], ClimateNetwork.SENSE_ICE);
					} else {
						//						blocksToSense[numOfBlockScans+1].infer(0);
						c.addEvidence(blocksToSense[numOfBlockScans+1], ClimateNetwork.SENSE_NONE);
					}
					System.out.println("Done");
				} else {
					System.out.println("Block already scanned twice");
				}

			}
			return true;
		} else {
			return false;
		}

	}

	public void runWorld(Agent a) {
		_agent = a;
		ClimateNetwork n = new ClimateNetwork(_regions, _p, _agent.getX(), _agent.getY());
		_blocksSensedCounter = new int[_board.length*_board[0].length];
		
		int numIces = 0;
		int numFlags = 0;
		for (int i=0; i<_board.length; i++){
			for (int j=0; j<_board[i].length; j++){
				if (_board[i][j]<=9 && _board[i][j]>=0){
					_ices.put(getCellNumber(i, j), numIces);
					numIces++;
				} else if (_board[i][j] == FLAG){
					_flags.put(getCellNumber(i,j), new Flag(i,j,numFlags));
					numFlags++;
				}
			}
		}
		_icesInverted = new int[_ices.size()];
		for (int i=0; i<_icesInverted.length; i++){
			for (Entry<Integer, Integer> en: _ices.entrySet()){
				if (en.getValue().intValue() == i){
					_icesInverted[i] = en.getKey().intValue();
				}
			}
		}
		_flagsInverted = new int[_flags.size()];
		for (int i=0; i<_flagsInverted.length; i++){
			for (Entry<Integer, Flag> en: _flags.entrySet()){
				if (en.getValue().getId() == i){
					_flagsInverted[i] = en.getKey().intValue();
				}
			}
		}

		MDPStateTable[] st = new MDP().compute(n, this, _movesLeft, _remember);

		DisplayWorld disp;
		synchronized (Boom.monitor) {
			disp = new DisplayWorld(getBoard(), a, _regions.length);
		}
		Header h;
		synchronized (Boom.monitor) {
			h = new Header(_regions.length, this, _icesInverted, _flagsInverted ,st, _remember);
			h.pack();
			h.setVisible(true);
		}

		n.enumerate();

		updateGuiProbs(disp, h, n);


		while(UserSense(n)){
			updateGuiProbs(disp, h, n);
		}
		
		h.setStateTable(null);
		h.setStateTable(new MDP().compute(n, this, _movesLeft, _remember));
		
	}

	private void updateGuiProbs(final DisplayWorld disp, Header h, ClimateNetwork n) {
		n.enumerate();

		int[] icyPatches = new int[n.getBlocks().size()];
		float[][] probabilities = new float[icyPatches.length][icyPatches.length];
		int index=0;
		for (Entry<Integer, Node[]> e: n.getBlocks().entrySet()){
			int cellId = e.getKey().intValue();
			icyPatches[index] = cellId;
			index++;
		}
		float maxVal = 0;
		for (int j=0; j<icyPatches.length; j++){
			Node y = n.getBlockArr(icyPatches[j])[0];
			float Py = y.getProb(ClimateNetwork.ICE);
			if (!n.isEvidence(y)){
				n.addEvidence(y, ClimateNetwork.ICE);
				n.enumerate();
				for (int i=0; i<icyPatches.length; i++){
					float Pxy = n.getBlockArr(icyPatches[i])[0].getProb(ClimateNetwork.ICE);
//					System.out.println("["+j+","+i+"]: "+Py+", "+ Pxy);
					probabilities[j][i] = round(Pxy*Py);
					if (maxVal < probabilities[j][i] && i!=j){
						maxVal = probabilities[j][i];
					}
				}
				n.removeEvidence(y);
				n.enumerate();
			}
		}

		System.out.println("Biggest chance for 2 cells to have ice is " + maxVal);

		h.updateProbJoint(probabilities);

		n.enumerate();

		float summerProb = n.getSeason().getProb(ClimateNetwork.SUMMER);
		float winterProb = n.getSeason().getProb(ClimateNetwork.WINTER);

		for(Entry<Integer, Node[]> entry: n.getBlocks().entrySet()){
			Node[] val = entry.getValue();
			float iceProb = val[0].getProb(ClimateNetwork.ICE);
			float s1Prob = val[1].getProb(ClimateNetwork.SENSE_ICE);
			float s2Prob = val[2].getProb(ClimateNetwork.SENSE_ICE);
			disp.setProb(entry.getKey(), iceProb, s1Prob, s2Prob);
		}


		h.updateProbSeason(summerProb, winterProb);
		Node[] climates = n.getClimates();
		for (int i=0; i<climates.length; i++){
			float warmProb = climates[i].getProb(ClimateNetwork.WARM);
			float coldProb = climates[i].getProb(ClimateNetwork.COLD);
			float frigidProb = climates[i].getProb(ClimateNetwork.FRIGID);
			h.updateProbClimate(i, warmProb, coldProb, frigidProb);
		}
		h.repaint();
		disp.repaint();
		disp.setSize(disp.getWidth(), disp.getHeight()+1);
		disp.setSize(disp.getWidth(), disp.getHeight()-1);
	}

	public int[][] getBoard() {
		return _board;
	}

	private static float round(float d){
		int decimalPlace = 4;
		BigDecimal bd = new BigDecimal(Float.toString(d));
		bd = bd.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
		return bd.floatValue();
	}

	public int getFlagId(int x, int y){
		return _flags.get(getCellNumber(x, y)).getId();
	}

	public int getIceId(int lastX, int lastY) {
		return _ices.get(getCellNumber(lastX, lastY));
	}
	
	public int getIceCellId(int id){
		return _icesInverted[id];
	}

	public int getNumFlags() {
		return _flags.size();
	}
	public int getNumIces() {
		return _ices.size();
	}

}


//TODO
//test the MDP on small boards